#ifndef __MCS__INPUT_STREAM__HPP
#define __MCS__INPUT_STREAM__HPP

#include "SourceLocation.hpp"
#include "Object.hpp"
#include <fstream>

/// Input character stream with automatic position tracking
class InputStream : public Object
{
public:
	InputStream(std::istream * stream, String const & fileName)
		: stream_(stream)
		, ownsStream_(false)
		, begin_(fileName, 1, 1)
		, end_(fileName, 1, 0)
		, afterEnd_(fileName, 1, 0)
		, char_(-1)
		, prevChar_(0)
		, state_(Consumed)
	{}

	InputStream(String const & fileName)
		: stream_()
		, ownsStream_(true)
		, begin_(fileName, 1, 1)
		, end_(fileName, 1, 0)
		, afterEnd_(fileName, 1, 0)
		, char_(-1)
		, prevChar_(0)
		, state_(Consumed)
	{
		stream_ = new std::fstream(fileName, std::ios_base::in);
	}

	~InputStream()
	{
		if(ownsStream_)
		{
			delete stream_;
		}
	}

	/// Peeks single character
	int peek()
	{
		if(state_ != Peeked)
		{
			if(state_ == Consumed)
			{
				prevChar_ = char_;
				char_ = stream_->get();
			}
			afterEnd_.grow(prevChar_);
			state_ = Peeked;
		}
		return char_;
	}

	/// Consumes peeked character
	void consume()
	{
		if(state_ != Consumed)
		{
			end_ = afterEnd_;
			state_ = Consumed;
		}
	}

	/// Discards peeked character
	void discard()
	{
		if(state_ != Discarded)
		{
			afterEnd_ = end_;
			state_ = Discarded;
		}
	}

	/// Returns position of the currently peeked char.
	SourcePosition position() const { return afterEnd_; }
	/// Returns location that was parsed since last call to 'startLocation()', including peeked character.
	SourceLocation location() const { return SourceLocation(begin_, afterEnd_); }
	/// Returns location from specified position up to the peeked character.
	SourceLocation locationFrom(SourcePosition const & p) const{ return SourceLocation(p, afterEnd_); }
	/// Returns location that contains only peeked character.
	SourceLocation cursorLocation() const { return SourceLocation(afterEnd_, afterEnd_); }
	/// Returns the same as 'location()' but without peeked character.
	SourceLocation consumedLocation() const { return SourceLocation(begin_, end_); }
	
	/// Marks current position() as the beginning of the location().
	void startParsing() { begin_ = afterEnd_; }
private:
	enum State { Consumed, Peeked, Discarded };
	std::istream * stream_;
	bool ownsStream_;
	SourcePosition begin_;
	SourcePosition end_;
	SourcePosition afterEnd_;
	int char_;
	char prevChar_;
	State state_;
};

/// Helper class for working with InputStream
class InputGetter
{
public:
	InputGetter(InputStream * stream)
		: stream_(stream)
	{
		ch_ = stream_->peek();
		consume_ = true;
	}

	~InputGetter()
	{
		if(consume_) stream_->consume();
		else stream_->discard();
	}

	bool eof() const { return ch_ == std::istream::traits_type::eof(); }
	int get() const { return ch_; }
	char ch() const { return (char)ch_; }

	void discard() { consume_ = false; }
	bool isDiscarded() const { return !consume_; }

	SourceLocation position() const { return stream_->cursorLocation(); }
private:
	InputStream * stream_;
	int ch_;
	bool consume_;
};

#endif //__MCS__INPUT_STREAM__HPP